﻿using SharpDX.Direct3D11;
using System.Windows.Forms;
using SharpDX;
using SharpDX.D3DCompiler;
using SharpDX.DXGI;
using SharpDX.Windows;
using Device = SharpDX.Direct3D11.Device;
using KDTrees;
using System.Runtime.InteropServices;
using ModelViewer;
using SharpDX.Direct3D;

namespace Engine
{
    public class MultiMeshContainer : ResourceContainer, System.IDisposable
    {
        public Serialize.MeshLink[] Meshes;
        public MaterialContainer[] Materials;
        public HardPoint[] Hardpoints;
        public MeshKDTree BSP;
        public Vector3 LocalAABBMax = new DVector3();
        public Vector3 LocalAABBMin = new DVector3();

        public Buffer Indices;
        public Buffer Vertexs;
        public VertexBufferBinding binding;

        public int VertexsCount, BytesPerVertex, FaceCount;

        ShaderConstants constants;
        Buffer constantsBuffer;

        private string DifuseName, SpecularMapName, NormalMapName, SelfMapName, UV2DifuseName, ShaderName;
        public MultiMeshContainer() { }

        public MultiMeshContainer(Engine.Serialize.MeshesContainer MC)
        {
            if (MC != null)
            {
                //Transform = Matrix.Scaling(1);

                Hardpoints = MC.HardPoints;
                if (MC.Materials != null && MC.Geometry != null && MC.Geometry.Meshes != null)
                {
                    BSP = MC.Geometry.BSP;
                    LocalAABBMax = MC.Geometry.AABBMax;
                    LocalAABBMin = MC.Geometry.AABBMin;

                    Meshes = new Serialize.MeshLink[MC.Materials.Length];
                    Materials = new MaterialContainer[MC.Materials.Length];
                    for (int i = 0; i < MC.Materials.Length; i++)
                    {
                        Meshes[i] = MC.Geometry.Meshes[i];
                        Materials[i] = new MaterialContainer(MC.Materials[i]);
                    }

                    VertexsCount = MC.Geometry.VertexCount;
                    BytesPerVertex = MC.Geometry.VertexData.Length / VertexsCount;
                    FaceCount = MC.Geometry.IndexData.Length / 12;

                    using (var vertices = new DataStream(BytesPerVertex * VertexsCount, true, true))
                    {
                        vertices.WriteRange<byte>(MC.Geometry.VertexData, 0, MC.Geometry.VertexData.Length);
                        vertices.Position = 0;
                        Vertexs = new Buffer(ModelViewer.Program.device, vertices, BytesPerVertex * VertexsCount, ResourceUsage.Default, BindFlags.VertexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
                        binding = new VertexBufferBinding(Vertexs, BytesPerVertex, 0);
                    }

                    using (var indices = new DataStream(4 * FaceCount * 3, true, true))
                    {
                        indices.WriteRange<byte>(MC.Geometry.IndexData, 0, MC.Geometry.IndexData.Length);
                        indices.Position = 0;
                        Indices = new Buffer(ModelViewer.Program.device, indices, 4 * FaceCount * 3, ResourceUsage.Default, BindFlags.IndexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
                    }


                    BufferDescription bd = new BufferDescription();
                    bd.SizeInBytes = Marshal.SizeOf(typeof(ShaderConstants));
                    bd.Usage = ResourceUsage.Dynamic;
                    bd.BindFlags = BindFlags.ConstantBuffer;
                    bd.CpuAccessFlags = CpuAccessFlags.Write;
                    bd.OptionFlags = ResourceOptionFlags.None;
                    bd.StructureByteStride = 0;

                    constantsBuffer = new Buffer(ModelViewer.Program.device, bd);
                    constants = new ShaderConstants();
                }
            }
        }

        public void DrawNoShadows(float TimeMili, Matrix Transform, Matrix VP, Vector3 LightDirection, Vector3 LightColor)
        {

            constants.WVP = Matrix.Transpose(Transform * VP);
            constants.World = Matrix.Transpose(Transform);
            constants.LightPosition = Conversion.ToVector4(LightDirection);//LShadow.Position - Camera.position);

            Program.context.InputAssembler.InputLayout = Materials[0].Effect.layout;
            Program.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
            Program.context.InputAssembler.SetVertexBuffers(0, binding);
            Program.context.InputAssembler.SetIndexBuffer(Indices, Format.R32_UInt, 0);

            DifuseName = "";
            SpecularMapName = "";
            NormalMapName = "";
            SelfMapName = "";
            UV2DifuseName = "";
            ShaderName = "";

            //NoDoubleSide
            for (int i = 0; i < Materials.Length; i++)
            {
                if(!Materials[i].DoubleSide)
                {
                    if (ShaderName != Materials[i].Effect.Name)
                    {
                        Materials[i].Effect.renderTechniques[0].GetPassByIndex(0).Apply(Program.context);
                        ShaderName = Materials[i].Effect.Name;
                        DifuseName = ""; SpecularMapName = ""; NormalMapName = "";
                        SelfMapName = ""; UV2DifuseName = ""; ShaderName = "";
                        Program.context.PixelShader.SetShaderResource(5, Program.MyScene.SkyBox.CubeSRV);
                        //Program.context.PixelShader.SetShaderResource(5, PSSMsHelper.SMArraySRV);

                        Program.context.PixelShader.SetSampler(5, DrawHelper.SamplLinWrapMip);
                    }

                    CahseSetTexture(Materials[i]);
                    constants.Color = Conversion.ToVector4(LightColor, Materials[i].Alpha / 255f);
                    SetConstants(false);
                    Program.context.DrawIndexed(Meshes[i].IndexCount, Meshes[i].StartIndex, 0);
                }
            }
            DifuseName = "";
            SpecularMapName = "";
            NormalMapName = "";
            SelfMapName = "";
            UV2DifuseName = "";
            ShaderName = "";
            //DoubleSide
            for (int i = 0; i < Materials.Length; i++)
            {
                if (Materials[i].DoubleSide)
                {
                    if (ShaderName != Materials[i].Effect.Name)
                    {
                        Materials[i].Effect.renderTechniques[0].GetPassByIndex(1).Apply(Program.context);
                        ShaderName = Materials[i].Effect.Name;
                        DifuseName = ""; SpecularMapName = ""; NormalMapName = "";
                        SelfMapName = ""; UV2DifuseName = ""; ShaderName = "";
                        Program.context.PixelShader.SetShaderResource(5, Program.MyScene.SkyBox.CubeSRV);
                        Program.context.PixelShader.SetSampler(5, DrawHelper.SamplLinWrapMip);
                    }

                    CahseSetTexture(Materials[i]);
                    constants.Color = Conversion.ToVector4(LightColor, Materials[i].Alpha / 255f);
                    SetConstants(false);
                    Program.context.DrawIndexed(Meshes[i].IndexCount, Meshes[i].StartIndex, 0);
                }
            }

        }

        public void DrawSplit(float TimeMili, Matrix MainWVP, Matrix[] CropMatrix, int StartIndex, int StopIndex)
        {

            constants.WVP = Matrix.Transpose(MainWVP);
            constants.Color = Conversion.ToVector4(new Vector3(), 0);
            constants.StartSplit = StartIndex;
            constants.StopSplit = StopIndex;

            unsafe
            {
                fixed (float* buffer = constants.cropMatrix)
                {
                    for (int x = 0; x < PSSMsHelper.SplitsNumber; x++)
                    {
                        buffer[x * 16 + 0 ] = CropMatrix[x].M11;
                        buffer[x * 16 + 1 ] = CropMatrix[x].M21;
                        buffer[x * 16 + 2 ] = CropMatrix[x].M31;
                        buffer[x * 16 + 3 ] = CropMatrix[x].M41;

                        buffer[x * 16 + 4 ] = CropMatrix[x].M12;
                        buffer[x * 16 + 5 ] = CropMatrix[x].M22;
                        buffer[x * 16 + 6 ] = CropMatrix[x].M32;
                        buffer[x * 16 + 7 ] = CropMatrix[x].M42;

                        buffer[x * 16 + 8 ] = CropMatrix[x].M13;
                        buffer[x * 16 + 9 ] = CropMatrix[x].M23;
                        buffer[x * 16 + 10] = CropMatrix[x].M33;
                        buffer[x * 16 + 11] = CropMatrix[x].M43;

                        buffer[x * 16 + 12] = CropMatrix[x].M14;
                        buffer[x * 16 + 13] = CropMatrix[x].M24;
                        buffer[x * 16 + 14] = CropMatrix[x].M34;
                        buffer[x * 16 + 15] = CropMatrix[x].M44;
                    }
                }
            }

            Program.context.InputAssembler.InputLayout = Materials[0].Effect.layout;
            Program.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
            Program.context.InputAssembler.SetVertexBuffers(0, binding);
            Program.context.InputAssembler.SetIndexBuffer(Indices, Format.R32_UInt, 0);

            //NoDoubleSide&NoAlphaTest
            {
                Materials[0].Effect.renderTechniques[1].GetPassByIndex(0).Apply(Program.context);

                SetConstants(true);

                for (int i = 0; i < Materials.Length; i++)
                {
                    if (!Materials[i].DoubleSide && Materials[i].Alpha < 1)
                    {
                        Program.context.DrawIndexed(Meshes[i].IndexCount, Meshes[i].StartIndex, 0);
                    }
                }
            }
            DifuseName = "";

            //NoDoubleSide&AlphaTest
            for (int i = 0; i < Materials.Length; i++)
            {
                if (!Materials[i].DoubleSide && Materials[i].Alpha >= 1)
                {
                    if (ShaderName != Materials[i].Effect.Name)
                    {
                        Materials[i].Effect.renderTechniques[1].GetPassByIndex(0).Apply(Program.context);
                        ShaderName = Materials[i].Effect.Name;
                        DifuseName = "";
                    }
                    if (DifuseName != Materials[i].Diffuse.Name)
                    {
                        Program.context.PixelShader.SetShaderResource(0, Materials[i].Diffuse.TextureResource);
                        Program.context.PixelShader.SetSampler(0, Materials[i].DiffuseSamplerState);
                        DifuseName = Materials[i].Diffuse.Name;
                    }

                    constants.Color = Conversion.ToVector4(new Vector3(), Materials[i].Alpha / 255f);

                    SetConstants(true);

                    Program.context.DrawIndexed(Meshes[i].IndexCount, Meshes[i].StartIndex, 0);
                }
            }

            //DoubleSide&AlphaTest
            for (int i = 0; i < Materials.Length; i++)
            {
                if (Materials[i].DoubleSide)
                {
                    if (ShaderName != Materials[i].Effect.Name)
                    {
                        Materials[i].Effect.renderTechniques[1].GetPassByIndex(1).Apply(Program.context);
                        ShaderName = Materials[i].Effect.Name;
                        DifuseName = "";
                    }
                    if (DifuseName != Materials[i].Diffuse.Name)
                    {
                        Program.context.PixelShader.SetShaderResource(0, Materials[i].Diffuse.TextureResource);
                        Program.context.PixelShader.SetSampler(0, Materials[i].DiffuseSamplerState);
                        DifuseName = Materials[i].Diffuse.Name;
                    }

                    constants.Color = Conversion.ToVector4(new Vector3(), Materials[i].Alpha / 255f);

                    SetConstants(true);

                    Program.context.DrawIndexed(Meshes[i].IndexCount, Meshes[i].StartIndex, 0);
                }
            }
        }

        public void DrawSplitShadows(float TimeMili, Matrix Transform, Matrix VP, Vector3 LightDirection, Vector3 LightColor,
            Matrix[] SMatrixs, float[] SplitDistances)
        {
            constants.WVP = Matrix.Transpose(Transform * VP);
            constants.World = Matrix.Transpose(Transform);
            constants.LightPosition = Conversion.ToVector4(LightDirection);//LShadow.Position - Camera.position);
            constants.H = new Vector4(PSSMsHelper.SplitShadowsSize, 0, 0, 0);


            unsafe
            {
                fixed (float* buffer = constants.cropMatrix)
                {
                    for (int x = 0; x < PSSMsHelper.SplitsNumber; x++)
                    {
                        buffer[x * 16 + 0] = SMatrixs[x].M11;
                        buffer[x * 16 + 1] = SMatrixs[x].M21;
                        buffer[x * 16 + 2] = SMatrixs[x].M31;
                        buffer[x * 16 + 3] = SMatrixs[x].M41;

                        buffer[x * 16 + 4] = SMatrixs[x].M12;
                        buffer[x * 16 + 5] = SMatrixs[x].M22;
                        buffer[x * 16 + 6] = SMatrixs[x].M32;
                        buffer[x * 16 + 7] = SMatrixs[x].M42;

                        buffer[x * 16 + 8] = SMatrixs[x].M13;
                        buffer[x * 16 + 9] = SMatrixs[x].M23;
                        buffer[x * 16 + 10] = SMatrixs[x].M33;
                        buffer[x * 16 + 11] = SMatrixs[x].M43;

                        buffer[x * 16 + 12] = SMatrixs[x].M14;
                        buffer[x * 16 + 13] = SMatrixs[x].M24;
                        buffer[x * 16 + 14] = SMatrixs[x].M34;
                        buffer[x * 16 + 15] = SMatrixs[x].M44;
                    }
                }

                fixed (float* buffer = constants.SplitPlane)
                {
                    for (int x = 0; x < PSSMsHelper.SplitsNumber; x++)
                        buffer[x*4] = SplitDistances[x];
                }
            }

            Program.context.InputAssembler.InputLayout = Materials[0].Effect.layout;
            Program.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
            Program.context.InputAssembler.SetVertexBuffers(0, binding);
            Program.context.InputAssembler.SetIndexBuffer(Indices, Format.R32_UInt, 0);

            DifuseName = "";
            SpecularMapName = "";
            NormalMapName = "";
            SelfMapName = "";
            UV2DifuseName = "";
            ShaderName = "";

            //NoDoubleSide
            for (int i = 0; i < Materials.Length; i++)
            {
                if (!Materials[i].DoubleSide)
                {
                    if (ShaderName != Materials[i].Effect.Name)
                    {
                        Materials[i].Effect.renderTechniques[2].GetPassByIndex(0).Apply(Program.context);
                        ShaderName = Materials[i].Effect.Name;
                        DifuseName = ""; SpecularMapName = ""; NormalMapName = "";
                        SelfMapName = ""; UV2DifuseName = ""; ShaderName = "";
                        Program.context.PixelShader.SetShaderResource(5, Program.MyScene.SkyBox.CubeSRV);
                        Program.context.PixelShader.SetSampler(5, DrawHelper.SamplLinWrapMip);
                        Program.context.PixelShader.SetShaderResource(6, PSSMsHelper.SMArraySRV);

                    }

                    CahseSetTexture(Materials[i]);

                    constants.Color = Conversion.ToVector4(LightColor, Materials[i].Alpha / 255f);

                    SetConstants(false);

                    Program.context.DrawIndexed(Meshes[i].IndexCount, Meshes[i].StartIndex, 0);
                }
            }
            DifuseName = "";
            SpecularMapName = "";
            NormalMapName = "";
            SelfMapName = "";
            UV2DifuseName = "";
            ShaderName = "";
            //DoubleSide
            for (int i = 0; i < Materials.Length; i++)
            {
                if (Materials[i].DoubleSide)
                {
                    if (ShaderName != Materials[i].Effect.Name)
                    {
                        Materials[i].Effect.renderTechniques[2].GetPassByIndex(1).Apply(Program.context);
                        ShaderName = Materials[i].Effect.Name;
                        DifuseName = ""; SpecularMapName = ""; NormalMapName = "";
                        SelfMapName = ""; UV2DifuseName = ""; ShaderName = "";
                        Program.context.PixelShader.SetShaderResource(5, Program.MyScene.SkyBox.CubeSRV);
                        Program.context.PixelShader.SetSampler(5, DrawHelper.SamplLinWrapMip);
                        Program.context.PixelShader.SetShaderResource(6, PSSMsHelper.SMArraySRV);
                    }

                    CahseSetTexture(Materials[i]);
                    constants.Color = Conversion.ToVector4(LightColor, Materials[i].Alpha / 255f);

                    SetConstants(false);

                    Program.context.DrawIndexed(Meshes[i].IndexCount, Meshes[i].StartIndex, 0);
                }
            }

        }

        private void CahseSetTexture(MaterialContainer MC)
        {
            if (DifuseName != MC.Diffuse.Name)
            {
                Program.context.PixelShader.SetShaderResource(0, MC.Diffuse.TextureResource);
                Program.context.PixelShader.SetSampler(0, MC.DiffuseSamplerState);
                DifuseName = MC.Diffuse.Name;
            }

            if (SpecularMapName != MC.SpecularMap.Name)
            {
                Program.context.PixelShader.SetShaderResource(1, MC.SpecularMap.TextureResource);
                Program.context.PixelShader.SetSampler(1, MC.SpecularMapSamplerState);
                SpecularMapName = MC.SpecularMap.Name;
            }

            if (NormalMapName != MC.NormalMap.Name)
            {
                Program.context.PixelShader.SetShaderResource(2, MC.NormalMap.TextureResource);
                Program.context.PixelShader.SetSampler(2, MC.NormalMapSamplerState);
                NormalMapName = MC.NormalMap.Name;
            }

            if (SelfMapName != MC.SelfMap.Name)
            {
                Program.context.PixelShader.SetShaderResource(3, MC.SelfMap.TextureResource);
                Program.context.PixelShader.SetSampler(3, MC.SelfMapSamplerState);
                SelfMapName = MC.SelfMap.Name;
            }

            if (UV2DifuseName != MC.UV2Diffuse.Name)
            {
                Program.context.PixelShader.SetShaderResource(4, MC.UV2Diffuse.TextureResource);
                Program.context.PixelShader.SetSampler(4, MC.UV2DiffuseSamplerState);
                UV2DifuseName = MC.UV2Diffuse.Name;
            }
        }

        public void DrawDepth(float TimeMili, Matrix MainWVP, EEDCamera Camera)
        {
            constants.WVP = Matrix.Transpose(MainWVP);
            constants.Color = new Vector4(0, 0, 0, 0);
            constants.CamPosition.Z = Camera.farClip;
            constants.CamPosition.W = Camera.nearClip;
            Program.context.InputAssembler.InputLayout = Materials[0].Effect.layout;
            Program.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
            Program.context.InputAssembler.SetVertexBuffers(0, binding);
            Program.context.InputAssembler.SetIndexBuffer(Indices, Format.R32_UInt, 0);

            //NoDoubleSide&NoAlphaTest
            {
                Materials[0].Effect.renderTechniques[1].GetPassByIndex(2).Apply(Program.context);

                SetConstants(false);

                for (int i = 0; i < Materials.Length; i++)
                {
                    if (!Materials[i].DoubleSide && Materials[i].Alpha < 1)
                        Program.context.DrawIndexed(Meshes[i].IndexCount, Meshes[i].StartIndex, 0);
                }
            }
            DifuseName = "";

            //NoDoubleSide&AlphaTest
            for (int i = 0; i < Materials.Length; i++)
            {
                if (!Materials[i].DoubleSide && Materials[i].Alpha >= 1)
                {
                    if (ShaderName != Materials[i].Effect.Name)
                    {
                        Materials[i].Effect.renderTechniques[1].GetPassByIndex(2).Apply(Program.context);
                        ShaderName = Materials[i].Effect.Name;
                        DifuseName = "";
                    }
                    if (DifuseName != Materials[i].Diffuse.Name)
                    {
                        Program.context.PixelShader.SetShaderResource(0, Materials[i].Diffuse.TextureResource);
                        Program.context.PixelShader.SetSampler(0, Materials[i].DiffuseSamplerState);
                        DifuseName = Materials[i].Diffuse.Name;
                    }

                    constants.Color = Conversion.ToVector4(new Vector3(), Materials[i].Alpha / 255f);
                    SetConstants(false);
                    Program.context.DrawIndexed(Meshes[i].IndexCount, Meshes[i].StartIndex, 0);
                }
            }

            //DoubleSide&AlphaTest
            for (int i = 0; i < Materials.Length; i++)
            {
                if (Materials[i].DoubleSide)
                {
                    if (ShaderName != Materials[i].Effect.Name)
                    {
                        Materials[i].Effect.renderTechniques[1].GetPassByIndex(3).Apply(Program.context);
                        ShaderName = Materials[i].Effect.Name;
                        DifuseName = "";
                    }
                    if (DifuseName != Materials[i].Diffuse.Name)
                    {
                        Program.context.PixelShader.SetShaderResource(0, Materials[i].Diffuse.TextureResource);
                        Program.context.PixelShader.SetSampler(0, Materials[i].DiffuseSamplerState);
                        DifuseName = Materials[i].Diffuse.Name;
                    }

                    constants.Color = Conversion.ToVector4(new Vector3(), Materials[i].Alpha / 255f);
                    SetConstants(false);
                    Program.context.DrawIndexed(Meshes[i].IndexCount, Meshes[i].StartIndex, 0);
                }
            }
        }


        private void SetConstants(bool Geometry)
        {
            var dataBox = Program.context.MapSubresource(constantsBuffer, 0, MapMode.WriteDiscard, SharpDX.Direct3D11.MapFlags.None);
            Utilities.Write(dataBox.DataPointer, ref constants);
            Program.context.UnmapSubresource(constantsBuffer, 0);

            Program.device.ImmediateContext.VertexShader.SetConstantBuffer(0, constantsBuffer);
            if (Geometry) Program.device.ImmediateContext.GeometryShader.SetConstantBuffer(0, constantsBuffer);
            Program.device.ImmediateContext.PixelShader.SetConstantBuffer(0, constantsBuffer);
        }

        private unsafe struct ShaderConstants
        {
            //Все должно быть выравненно по 16
            public Matrix World;//мировая
            public Matrix WVP;
            public Vector4 LightPosition;
            public Vector4 CamPosition;
            public Vector4 temp;
            public Vector4 Color;
            public Vector4 H;
            public int StartSplit;
            public int StopSplit;
            public int FreeUInt0;
            public int FreeUInt1;
            public fixed float cropMatrix[PSSMsHelper.SplitsNumber * 16];
            public fixed float SplitPlane[12*4];
        }

        #region IDisposable
        ~MultiMeshContainer()
        {
            Dispose();
        }

        public void Dispose()
        {
            Indices.Dispose();
            Vertexs.Dispose();
            if (constantsBuffer != null) constantsBuffer.Dispose();
        }
        #endregion
    }
}
